package timing.ukulele.config;

import org.springframework.context.annotation.Bean;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.context.NoOpServerSecurityContextRepository;
import reactor.core.publisher.Mono;
import timing.ukulele.filter.JwtTokenAuthenticationFilter;
import timing.ukulele.handler.AuthenticationFailHandler;
import timing.ukulele.handler.AuthenticationSuccessHandler;
import timing.ukulele.handler.CustomHttpBasicServerAuthenticationEntryPoint;
import timing.ukulele.handler.TimingLogoutSuccessHandler;
import timing.ukulele.service.SecurityUserDetailService;

@EnableWebFluxSecurity
public class SecurityConfig {

    private static final String[] excludeAuthPages = {"/auth/login"};

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public ReactiveAuthenticationManager reactiveAuthenticationManager(SecurityUserDetailService userDetailsService, PasswordEncoder passwordEncoder) {
        UserDetailsRepositoryReactiveAuthenticationManager authenticationManager = new UserDetailsRepositoryReactiveAuthenticationManager(userDetailsService);
        authenticationManager.setPasswordEncoder(passwordEncoder);
        return authenticationManager;
    }

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http, JwtProperties jwtProperties,
                                                         CustomHttpBasicServerAuthenticationEntryPoint authenticationEntryPoint,
                                                         AuthenticationSuccessHandler successHandler, AuthenticationFailHandler failHandler,
                                                         TimingLogoutSuccessHandler logoutSuccessHandler,
                                                         ReactiveAuthenticationManager reactiveAuthenticationManager) {
        http.csrf(ServerHttpSecurity.CsrfSpec::disable)
                .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
                .authenticationManager(reactiveAuthenticationManager)
                .exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint)
                .accessDeniedHandler((swe, e) -> {
                    swe.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
                    return swe.getResponse().writeWith(Mono.just(new DefaultDataBufferFactory().wrap("FORBIDDEN".getBytes())));
                })
                .and()
                .securityContextRepository(NoOpServerSecurityContextRepository.getInstance())
                .authorizeExchange()
                .pathMatchers(excludeAuthPages).permitAll()
                .pathMatchers(HttpMethod.OPTIONS).permitAll()
                .anyExchange()
                .authenticated()
                .and()
                .formLogin().loginPage("/auth/login")
                .authenticationSuccessHandler(successHandler)
                .authenticationFailureHandler(failHandler)
                .and()
                .logout().logoutUrl("/auth/logout")
                .logoutSuccessHandler(logoutSuccessHandler)
                .and()
                .addFilterAt(new JwtTokenAuthenticationFilter(jwtProperties), SecurityWebFiltersOrder.HTTP_BASIC);
        return http.build();
    }
}
